package sf.database.jdbc.sql;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sf.common.exception.SmallOrmException;
import sf.core.DBField;
import sf.core.DBObject;
import sf.database.dbinfo.Feature;
import sf.database.dialect.DBDialect;
import sf.database.meta.ColumnMapping;
import sf.database.meta.TableMapping;
import sf.database.meta.TupleField;
import sf.database.util.OrmValueUtils;
import sf.spring.util.Assert;
import sf.tools.StringUtils;

import javax.persistence.GenerationType;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 一个可以更新数据的结果集，我们可以直接在结果集上更新数据。 代码示例如下:
 *
 * <pre>
 * <code>
 *
 * RecordsHolder<Root> holder = db.selectForUpdate(QB.create(Root.class).getInstance());
 * System.out.println("总数:" + holder.size());
 * int n = 0;
 * for (Root r : holder.get()) {
 * r.setName("更新第" + n + "条。"); // 修改对象中的值
 * n++;
 * }
 * holder.delete(holder.size() - 1); // 删除结果集中的最后一条记录(序号从0开始)
 * Root root = holder.newRecord(); // 创建一条新纪录
 * root.setName("新插入的记录");
 * holder.commit(); // 提交上述修改并关闭游标（更新、删除、添加）
 * </code>
 * </pre>
 * @param <T>
 * @author
 */
public final class RecordsHolder<T extends DBObject> implements Iterable<T> {
    private static final Logger logger = LoggerFactory.getLogger(RecordsHolder.class);
    public static final int BEFORE_FIRST = -2;
    public static final int AFTER_LAST = -3;

    private DBDialect profile;

    private ResultSet rs;// 必须是一个允许前后滚动的结果集
    private Connection conn;

    private List<RecordHolder<T>> rhs;
    private List<T> objs;
    private boolean noHoldInsertValues = true;// HSQL has a feature, if you move
    // the cursor to InsertRow, then
    // you can not move it to any
    // other rows. so we must pend
    // inert request to last.
    private boolean supportsNewRec = true;// Derby BUG,
    // 用结果集直接插入记录后，Derby的自增主键不会同步增长，造成后续自增主键出现冲突。
    private TableMapping meta;
    private int index = BEFORE_FIRST;

    /**
     * 返回结果的条数
     * @return
     */
    public int size() {
        ensureOpen();
        return objs.size();
    }

    /**
     * 返回结果集
     * @return
     */
    public List<T> get() {
        ensureOpen();
        return objs;
    }

    RecordHolder<T> get(int i) {
        ensureOpen();
        return rhs.get(i);
    }

    RecordsHolder(TableMapping mm) throws SQLException {
        this.meta = mm;
        Assert.notNull(meta, "");
    }

    void init(ResultSet holder, List<T> objs, DBDialect profile) {
        this.profile = profile;
        this.rs = holder;
        this.objs = objs;
        rhs = new ArrayList<RecordHolder<T>>();
        for (int i = 0; i < objs.size(); i++) {
            rhs.add(new RecordHolder<T>(this, i, objs.get(i)));
        }
        if (profile.has(Feature.NOT_FETCH_NEXT_AUTOINCREAMENTD)) {
            for (ColumnMapping type : meta.getPkFields()) {
                if (type.isAutoNumber()) {
                    supportsNewRec = false;
                }
            }
        }
        noHoldInsertValues = profile.notHas(Feature.CURSOR_ENDS_ON_INSERT_ROW);
    }

    /**
     * 一些数据库不支持在ResultSet上新建记录，返回该标志
     * @return
     */
    public boolean supportsNewRecord() {
        return supportsNewRec;
    }

    /**
     * 创建一个新记录
     * @return
     */
    public T newRecord() {
        ensureOpen();
        if (!supportsNewRec) {
            throw new UnsupportedOperationException("Current database " + profile.getName() + " not support 'newRecord()'");
        }
        @SuppressWarnings("unchecked")
        T obj = null;
        try {
            obj = (T) meta.getThisType().getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new SmallOrmException(e);
        }
        obj.startUpdate();
        if (!meta.getPkFields().isEmpty()) {
            for (ColumnMapping type : meta.getPkFields()) {
                if (type.getGv() != null) {
                    if (type.isAutoNumber()) {
                        OrmValueUtils.setValue(obj, type, getNextAutoIncreament(type));
                    } else if (type.getClz() == String.class) {
                        String value = StringUtils.uuid32();
                        OrmValueUtils.setValue(obj, type, value);
                    }
                }
            }
        }
        // 准备完成，加入
        RecordHolder<T> r = new RecordHolder<T>(this, -1, obj);
        this.rhs.add(r);
        return obj;
    }

    /*
     * 计算自增值
     * @param mapping
     * @return
     */
    private long getNextAutoIncreament(ColumnMapping mapping) {
        GenerationType gtype = mapping.getGv().strategy();
        if (gtype == GenerationType.SEQUENCE || gtype == GenerationType.TABLE) {
            try {
                //TODO 未实现
                return 0;
            } catch (Exception e) {
                throw new PersistenceException(e);
            }
        } else {
            return profile.getColumnAutoIncreamentValue(mapping, conn);
        }
    }

    /**
     * 检查指定的对象是否删除
     * @param index 序号，从0开始
     * @return
     */
    public boolean isDeleted(int index) {
        ensureOpen();
        if (index < 0 || index >= size()) {
            throw new IllegalArgumentException("the object you want to delete is not exist in the list");
        }
        RecordHolder<T> r = rhs.get(index);
        return r.status == RecordHolder.DELETE || r.status == RecordHolder.DELETED;
    }

    /**
     * 删除
     * @param index 在结果集中的序号
     */
    public void delete(int index) {
        ensureOpen();
        if (index < 0 || index >= size()) {
            throw new IllegalArgumentException("the object you want to delete is not exist in the list");
        }
        RecordHolder<T> r = rhs.get(index);
        Assert.notNull(r, "");
        r.status = RecordHolder.DELETE;
    }

    /**
     * 删除指定的记录
     * @param object 删除结果集当中指定的对象
     */
    public void delete(T object) {
        ensureOpen();
        int index = objs.indexOf(object);
        delete(index);
    }

    /**
     * 将修改提交(更新到数据库)
     * @param closeit 提交后关闭ResultSet
     * @throws SQLException
     */
    private int commit(boolean closeit) throws SQLException {
        ensureOpen();
        moveTo(BEFORE_FIRST);
        List<RecordHolder<T>> toInsert = new ArrayList<RecordHolder<T>>();
        int changed = 0;
        for (RecordHolder<T> r : rhs) {
            try {
                switch (r.getStatus()) {
                    case RecordHolder.CACNLED:// 不用操作
                        break;
                    case RecordHolder.DELETE:
                        moveTo(r.index);
                        rs.deleteRow();
                        changed++;
                        break;
                    case RecordHolder.DELETED:// 不用操作
                        break;
                    case RecordHolder.INSERT:
                        if (closeit || noHoldInsertValues) {
                            toInsert.add(r);
                            changed++;
                        }
                        break;
                    case RecordHolder.INSERTED:// 不用操作
                        break;
                    default:
                        if (r.get().needUpdate()) {
                            update(r.get(), r.index);
                            changed++;
                        }
                }
            } catch (SQLException e) {
                logger.error("Error update " + r);
                throw e;
            }
        }
        // Insert最后再操作
        for (RecordHolder<T> r : toInsert) {
            insert(r.get());
            r.setInserted();
            changed++;
        }
        if (closeit)
            close();
        return changed;
    }

    /**
     * 将修改提交(更新到数据库)
     * @throws SQLException
     */
    public int commit() {
        try {
            return commit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将修改提交到数据库
     * @return
     */
    public int commitAndClose() {
        try {
            return commit(true);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void update(T t, int index2) throws SQLException {
        if (index2 < 0 || index2 >= size()) {
            throw new SQLException("the index " + index2 + " is invalid!");
        }
        moveTo(index2);
        boolean flag = false;
        for (DBField f : t.updateValueMap().keySet()) {
            if (f instanceof Enum<?> || f instanceof TupleField) {
                Object value = t.updateValueMap().get(f);
                ColumnMapping column = meta.getSchemaMap().get(f);
                String columnName = column.getRawColumnName();
                if (value == null) {
                    rs.updateNull(columnName);
                } else {
                    column.getHandler().update(rs, columnName, value);
                    // rs.updateObject(columnName, value);
                }

                flag = true;
            }
        }
        if (flag)
            rs.updateRow();
    }

    private void insert(T t) throws SQLException {
        rs.moveToInsertRow();
        Assert.notNull(meta, "");
        for (ColumnMapping mType : meta.getSchemas()) {
            String columnName = mType.getRawColumnName();
            Object value = OrmValueUtils.getValue(t, mType);
            mType.getHandler().update(rs, columnName, value);
        }
        rs.insertRow();
        rs.moveToInsertRow();
    }

    private void moveTo(int to) throws SQLException {
        // rs.moveToInsertRow();
        if (to == index)
            return;
        if (to == BEFORE_FIRST) {
            rs.beforeFirst();
            this.index = to;
        } else if (to == AFTER_LAST) {
            rs.afterLast();
            this.index = to;
        } else if (index == AFTER_LAST) {
            rs.first();
            this.index = 0;
        } else {// to>-1
            if (index == AFTER_LAST || index == BEFORE_FIRST) {
                rs.first();
                index = 0;
            }
            while (index < to) {
                boolean flag = rs.next();
                index++;
                if (!flag)
                    break;
            }
            while (index > to) {
                boolean flag = rs.previous();
                index--;
                if (!flag)
                    break;
            }
            if (index == to)
                return;
            throw new RuntimeException("Can not move to index :" + index + "->" + to);
        }
    }

    private void ensureOpen() {
        try {
            if (rs == null || rs.isClosed()) {
                throw new IllegalArgumentException("The result set has been closed.");
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 关闭ResultSet和statement
     * @throws SQLException
     */
    public void close() {
        if (rs != null) {
            try {
                rs.close();
                rs = null;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }

        }
    }

    @Override
    public Iterator<T> iterator() {
        ensureOpen();
        return objs.iterator();
    }
}
